/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: support fiq-gic
 * Author: xiaojiangfeng <xiaojiangfeng@huawei.com>
 * Create: 2018-11-20
 */

#include "../hal/fiq_glue_com/fiq_glue_private.h"

static DEFINE_SPINLOCK(fiq_set_lock);

static void gic_clear_pending(struct irq_data *d)
{
	void __iomem *reg;
	u32 mask;

	reg = gic_dist_base(d) + GIC_DIST_PENDING_CLEAR + (gic_irq(d) / 32) * 4;
	mask = 1 << (gic_irq(d) % 32);
	writel_relaxed(mask, reg);
}

static void gic_set_priority(struct irq_data *d, unsigned int priority)
{
	void __iomem *reg;
	u32 shift, val, mask;

	reg = gic_dist_base(d) + GIC_DIST_PRI + (gic_irq(d) & ~3);
	shift = (gic_irq(d) % 4) * 8;
	mask = 0xff << shift;

	if (priority > 0xff)
		priority = 0xff;

	val = readl_relaxed(reg);
	val &= ~mask;
	val |= priority << shift;
	writel_relaxed(val, reg);
}

static void gic_set_group(struct irq_data *d, bool group)
{
	void __iomem *reg;
	u32 shift, val, mask;
	unsigned int group_tmp;

	group_tmp = (unsigned int)group;

	reg = gic_dist_base(d) + GIC_DIST_IGROUP + (gic_irq(d) / 32) * 4;
	shift = gic_irq(d) % 32;
	mask = 1 << shift;

	val = readl_relaxed(reg);
	val &= ~mask;
	val |= group_tmp << shift;
	writel_relaxed(val, reg);
}

static void gic_raise_softfiq(void)
{
	/* send ipi0 to other all cpus */
	if (GIC_NONSECURITY_GROUP == gic_irq_group)
		writel_relaxed(0x1 << 24, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
}

static int gic_get_irqnum(void)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
	irqnr = irqstat & GICC_IAR_INTID;

	return irqnr;
}

static void gic_eoi_fiq(unsigned int fiq)
{
	writel_relaxed(fiq, gic_data_cpu_base(&gic_data[0]) + GIC_CPU_EOI);
}

static int gic_set_fiq(unsigned int fiq, unsigned int priority)
{
	struct irq_desc *desc = NULL;
	struct irq_data *data = NULL;
	unsigned long flags;

	if (GIC_SECURITY_GROUP == gic_irq_group) {
		/* when normal irq is in security group0,
		 * we can't set fiq to security group0.
		 */
		printk(KERN_ERR"Gic irq gourp is security, can't set fiq.\n");
		return -EPERM;
	}

	desc = irq_to_desc(fiq);
	data = irq_desc_get_irq_data(desc);
	if (!data->chip_data) {
		/* if the irq  is ipi, chip_data is NULL.
		 * we can't use this function to set it.
		 */
		printk(KERN_ERR"fiq%d chip data is NULL.\n", fiq);
		return -EINVAL;
	}

	/* set fiq to gic security group0 */
	spin_lock_irqsave(&fiq_set_lock, flags);
	gic_mask_irq(data);
	gic_clear_pending(data);
	gic_set_group(data, GIC_SECURITY_GROUP);
	gic_set_priority(data, priority);
	gic_unmask_irq(data);
	spin_unlock_irqrestore(&fiq_set_lock, flags);
	return 0;
}

static void gic_fiq_init(void)
{
	struct fiq_gic_handle fiq_handle = {
		.set_fiq		= gic_set_fiq,
		.raise_softfiq		= gic_raise_softfiq,
		.get_fiq_num		= gic_get_irqnum,
		.eoi_fiq		= gic_eoi_fiq,
	};

	fiq_register_gic(&fiq_handle);
}
